# -*- coding: utf-8 -*-
# Copyright 2011-2012 MagiCycles.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os
import time
import urllib
import logging

from time import gmtime
from datetime import datetime

import webapp2
from webapp2_extras import json

from google.appengine.ext import db
from google.appengine.api import urlfetch
from google.appengine.ext import blobstore
from google.appengine.api import memcache
from google.appengine.ext.webapp import blobstore_handlers

from google.appengine.api import conf
APP_VERSION, CURRENT_CONFIG_VERSION, DEVELOPMENT = conf._inspect_environment()

if DEVELOPMENT:
    ENABLE_ANONYMOUS_UPLOAD = False
    UPLOAD_PHOTO_LIMIT = 8*1024*1024  # 8M
else:
    ENABLE_ANONYMOUS_UPLOAD = False
    UPLOAD_PHOTO_LIMIT = 2*1024*1024  # 2M

def _dump_date(d, delim):
    """Used for `http_date` and `cookie_date`."""
    if d is None:
        d = gmtime()
    elif isinstance(d, datetime):
        d = d.utctimetuple()
    elif isinstance(d, (int, long, float)):
        d = gmtime(d)
    return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
        ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
        d.tm_mday, delim,
        ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
         'Oct', 'Nov', 'Dec')[d.tm_mon - 1],
        delim, str(d.tm_year), d.tm_hour, d.tm_min, d.tm_sec
    )

def http_date(timestamp=None):
    return _dump_date(timestamp, ' ')

class UploadFiles(db.Model):
    blob_key = db.StringProperty(default="")
    active = db.BooleanProperty(default=False)
    deleted = db.BooleanProperty(default=False)
    filename = db.StringProperty()
    type = db.StringProperty(default="application/octet-stream")
    md5_hash = db.StringProperty()
    upload_date = db.DateTimeProperty(auto_now_add=True)
    hits = db.IntegerProperty(default=0)
    user = db.StringProperty()

    key_template = "UploadFiles/%(signature)s"

    @property
    def signature(self):
        return self.key().name()[12:]

    @classmethod
    def gen_key_name(cls, **kw):
        try:
            return cls.key_template % kw
        except KeyError:
            logging.warn('generate key_name failed: %s <- %s',
                         cls.key_template, kw)

    @classmethod
    def check_exist(cls, signature):
        key_name = cls.gen_key_name(signature=signature)
        return cls.get_by_key_name(key_name)

    @classmethod
    def new_upload_file(cls, signature, user):
        key_name = cls.gen_key_name(signature=signature)
        upload_file = cls(key_name=key_name, signature=signature, user=user)
        upload_file.put()
        return upload_file

    @classmethod
    def check_md5_hash(cls, md5_hash):
        res = cls.all().filter("md5_hash =", md5_hash).fetch(1)
        return res and res[0]

    @classmethod
    def write_upload_file(cls, signature, blob_info):
        upload_file = cls.check_exist(signature=signature)
        if upload_file:
            same_md5_file = cls.check_md5_hash(blob_info.md5_hash)
            if same_md5_file:
                blob_key = str(same_md5_file.blob_key)
            else:
                blob_key = str(blob_info.key())
            def txn():
                upload_file.blob_key = blob_key
                upload_file.active = True
                upload_file.filename = unicode( blob_info.filename, "utf-8" )
                upload_file.type = blob_info.content_type
                upload_file.md5_hash = blob_info.md5_hash
                upload_file.put()
                return upload_file
            upload_file = db.run_in_transaction(txn)
            if same_md5_file:
                blob_info.delete()
            return upload_file
        else:
            raise db.EntityNotFoundError()

    @classmethod
    def add_hits_count(cls, signature, hits=0):
        def txn():
            upload_file = cls.check_exist(signature)
            if upload_file:
                upload_file.hits += hits
                upload_file.put()
            return upload_file

        return db.run_in_transaction(txn)

    @classmethod
    def clean_unused_data(cls):
        unused_keys = []
        unused_blob = {}

        query = UploadFiles.all().filter('active = ', False)
        for f in query:
            unused_blob[f.md5_hash] = f.blob_key
            unused_keys.append(str(f.key()))
        query = UploadFiles.all().filter('hits = ', 0)
        for f in query:
            unused_blob[f.md5_hash] = f.blob_key
            unused_keys.append(str(f.key()))
        query = UploadFiles.all().filter('deleted = ', True)
        for f in query:
            unused_blob[f.md5_hash] = f.blob_key
            unused_keys.append(str(f.key()))

        unused_keys = list(set(unused_keys))
        unused_keys and db.delete(unused_keys)

        unused_blob_keys = []
        for md5, blob_key in unused_blob.iteritems():
            if not cls.check_md5_hash(md5):
                unused_blob_keys.append(blob_key)

        unused_blob_keys = list(set(unused_blob_keys))
        unused_blob_keys and blobstore.delete(unused_blob_keys)

    @classmethod
    def get_top_hits(cls, top=100):
        return UploadFiles.all().order("-hits").fetch(top)
		

def check_signature(sign, rest_url=None):
    # check signature with Magicycles main site
    if ENABLE_ANONYMOUS_UPLOAD:
        return "CC_DEBUG"
    sign = str(urllib.unquote(sign))
    rest_url = rest_url or "http://localhost:8080/apis/rest/"#?method=check_upload_signature"

    rest_request = {
      "method": "check_upload_signature",
      "signature": sign,
    }
    rest_request = urllib.urlencode(rest_request)

    result = None
    for retry in xrange(3):
        try:
            result = urlfetch.fetch(url=rest_url,
                                    payload=rest_request,
                                    method=urlfetch.POST,
                                    deadline=10)
            break
        except urlfetch.DownloadError:
            logging.exception("check_signature retry:")

    if result and result.status_code == 200:
        logging.info(result.content)
        result = json.decode(result.content)
        if result["status"] == "ok":
            return result["user"]
            #return True

    return False


class MainHandler(webapp2.RequestHandler):
    def get(self):
        self.redirect("/static/test.html")


class GetUploadUrlHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/javascript"
        signature = self.request.get("signature", None)
        host_url = self.request.get("host",None)

        logging.info("GetUploadUrlHandler:%s, %s"%(signature,host_url))

        result = "var result='error';"
        msg = "var message='wrong upload signature';"
        try:
            if signature:
                user = check_signature(signature, host_url)
                if user:
                    signature = str(urllib.unquote(signature))
                    db_uploadfile = UploadFiles.check_exist(signature)
                    if not db_uploadfile:
                        result = "var result='ok';"
                        msg ="var uploadurl='%s';"%(blobstore.create_upload_url('/upload'))
                        UploadFiles.new_upload_file(signature, user=user)
                    else:
                        msg = "var message='used upload signature'"
        except Exception,e:
             result = "var result='error';"
             msg = "var message='%s';"%str(e)

        self.response.out.write(result)
        self.response.out.write(msg)
            
		
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload_files = self.get_uploads() 
        
        if not upload_files:
            msg = "no upload file"
            self.redirect("/upload?result=error&msg=%s"%(msg))
            
        blob_info = upload_files[0]
        signature = self.request.get("signature", None)

        if not signature:
            blob_info.delete()
            msg = "wrong signature"
            self.redirect("/upload?result=error&msg=%s"%(msg))

        if blob_info.size >= UPLOAD_PHOTO_LIMIT:
            blob_info.delete()
            msg = "file size exceed"
            self.redirect("/upload?result=error&msg=%s"%(msg))

        signature = str(urllib.unquote(signature))
        logging.info("UploadHandler:%s"%signature)

        db_uploadfile = UploadFiles.check_exist(signature)
        if not db_uploadfile:  #check signature
            blob_info.delete()
            msg = "wrong signature"
            self.redirect("/upload?result=error&msg=%s"%(msg))

        if db_uploadfile.active:
            blob_info.delete()
            msg = "used signature"
            self.redirect("/upload?result=error&msg=%s"%(msg))

        UploadFiles.write_upload_file(signature, blob_info)

        msg = "upload succeeded"
        self.redirect("/upload?result=ok&msg=%s"%(msg))
            
    def get(self):
        result = self.request.get("result", "error")
        msg = self.request.get("msg", "")
        if result == "ok":
            self.response.out.write(msg)
        else:
            self.response.set_status(405)
            self.response.out.write(msg)

CACHE_TIME = 3600*24*30
MEMCACHE_TEMPLATE = "blob_info_%s"

class BlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, signature):
        signature = str(urllib.unquote(signature))
        key = MEMCACHE_TEMPLATE%signature
        blob_info = memcache.get(key)
        if not blob_info:
            uploadfile = UploadFiles.add_hits_count(signature, 1)
            if uploadfile:
                blob_info = blobstore.BlobInfo.get(uploadfile.blob_key)
                if blob_info:
                    try:
                        memcache.set(key, blob_info)
                    except Exception:
                        pass

        self.response.headers['Access-Control-Allow-Origin'] = '*'
        if blob_info:
            logging.info("download file: %s %s"%(blob_info.filename, signature))

            self.response.headers['Date'] = http_date()
            self.response.headers['Cache-Control'] = 'max-age=%d, public' % CACHE_TIME
            self.response.headers['Expires'] = http_date(time.time() + CACHE_TIME)
            try:
                if blob_info.content_type.find("image") >= 0:
                    self.send_blob(blob_info, content_type=blob_info.content_type)
                else:
                    self.send_blob(blob_info, content_type=blob_info.content_type, save_as=blob_info.filename)
            except Exception:
                logging.exception("exception in send_blob:")
                self.error(500)
        else:
            self.error(404)
            self.response.out.write('File Not Found')

class GetFileUrlHandler(webapp2.RequestHandler):
    def get(self, signature):
        result = "error"
        fileurl = "http://%s/static/error.gif"%os.environ["HTTP_HOST"]

        timeout = self.request.get("timeout", 30)
        try:
            timeout = long(timeout)
        except Exception:
            timeout = 30

        self.response.headers["Content-Type"] = "text/javascript"
        signature = str(urllib.unquote(signature))
        start_time = datetime.now()
        while (datetime.now()-start_time).total_seconds() < timeout:
            uploadfile = UploadFiles.check_exist(signature=signature)
            if not uploadfile:
                break
            if uploadfile.active:
                result = "ok"
                fileurl = ('http://%s/files/%s'%(os.environ["HTTP_HOST"], signature))
                break
            time.sleep(3)

        self.response.out.write("var result='%s';"%(result))
        self.response.out.write("var fileurl='%s';"%(fileurl))
        self.response.out.write("var photourl='%s';"%(fileurl))

def cache_top_hits():
    logging.info("cache_top_hits")
    top_hits_files = UploadFiles.get_top_hits(50)
    for file in top_hits_files:
        key = MEMCACHE_TEMPLATE%file.signature
        blob_info = memcache.get(key)
        if not blob_info:
            blob_info = blobstore.BlobInfo.get(file.blob_key)
            if blob_info:
                try:
                    memcache.set(key, blob_info)
                except Exception:
                    pass
    logging.info("CacheTopHits done! %s files cached"%(len(top_hits_files)))

def clean_unused_data():
    logging.info("clean_unused_data")
    UploadFiles.clean_unused_data()
    logging.info("clean_unused_data done")

Backend_Actions = {
    "cache_top_hits" : cache_top_hits,
    "clean_unused_data": clean_unused_data,
}
class BackendHandler(webapp2.RequestHandler):
    def get(self, action):
        method = Backend_Actions.get(action, None)
        if method:
            method()

app = webapp2.WSGIApplication(
      [('/', MainHandler),
       ('/getuploadurl/', GetUploadUrlHandler),
       ('/upload.*', UploadHandler),
       ('/getfileurl/([^/]+)?', GetFileUrlHandler),
       ('/getphotourl/([^/]+)?', GetFileUrlHandler),
       ('/files/([^/]+)?', BlobHandler),
      ], debug=True)

backend_app = webapp2.WSGIApplication(
    [
        ('/backend/([^/]+)?', BackendHandler),
    ], debug=True
)

def main():
    pass

if __name__ == '__main__':
  main()
